﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using MaxZhang.EasyEntities.Dynamic.Aop;

namespace MaxZhang.EasyEntities.Dynamic
{
    public class DynamicPropertyAccessor
    {
        protected static CacheManager<object> cacheManager =
           new CacheManager<object>( new SimpleCache<object>(), null);



        private Func<object, object> m_getter;

        private Action<object, object> m_setter;

   
        public DynamicPropertyAccessor(Type type, string propertyName)
            : this(type.GetProperty(propertyName))
        {
          
        }

        public DynamicPropertyAccessor(PropertyInfo propertyInfo)
        {
            
            //生成get属性缓存
            StringBuilder keyg=new StringBuilder();
            keyg.Append(propertyInfo.DeclaringType.FullName).Append(propertyInfo.Name).Append("get");
            string key =  keyg.ToString();
            var getProperty = cacheManager.Get(key);
            if (getProperty!=null)
            {
                this.m_getter = (Func<object, object>)getProperty;
            }
            else
            {
                //--------------------------------------------------------
                //get{return {Property}}

                // target: (object)((({TargetType})instance).{Property})

                // preparing parameter, object type
                ParameterExpression instance = Expression.Parameter(
                    typeof(object), "instance");

                // ({TargetType})instance
                Expression instanceCast = Expression.Convert(
                    instance, propertyInfo.ReflectedType);

                // (({TargetType})instance).{Property}
                Expression propertyAccess = Expression.Property(
                    instanceCast, propertyInfo);

                // (object)((({TargetType})instance).{Property})
                UnaryExpression castPropertyValue = Expression.Convert(
                    propertyAccess, typeof(object));

                // Lambda expression
                Expression<Func<object, object>> lambda =
                    Expression.Lambda<Func<object, object>>(
                        castPropertyValue, instance);

                var newCacheLambda = lambda.Compile();
                cacheManager.Set(key,newCacheLambda);
                this.m_getter = newCacheLambda;
            }
            //-------------------------------------------------------
            //set{{Property}=value;}
            //生成set属性缓存
            StringBuilder keySet = new StringBuilder();
            keySet.Append(propertyInfo.DeclaringType.FullName).Append(propertyInfo.Name).Append("set");
            string key2 = keySet.ToString();
            var setProperty = cacheManager.Get(key2);
            if (setProperty != null)
            {
                this.m_setter = (Action<object, object>)setProperty;
            }
            else
            {
                try
                {
                    var param_obj = Expression.Parameter(typeof (object), "obj");
                    var param_val = Expression.Parameter(typeof (object), "val");

                    var body_obj = Expression.Convert(param_obj, propertyInfo.ReflectedType);
                    var body_val = Expression.Convert(param_val, propertyInfo.PropertyType);
                    var body = Expression.Call(body_obj, propertyInfo.GetSetMethod(), body_val);

                    Expression<Action<object, object>> setLambda =
                        Expression.Lambda<Action<object, object>>(
                            body, param_obj, param_val);


                    var newCacheLambda = setLambda.Compile();
                    this.m_setter = newCacheLambda;
                    cacheManager.Set(key2, newCacheLambda);
                }
                catch
                {
                }
            }

        }

        public object GetValue(object o)
        {
            return this.m_getter(o);
        }

        public void SetValue(object obj,object val)
        {
            this.m_setter(obj, val);
        }

    }

}
